package com.lateralminds.alfresco.util;

import java.util.Comparator;
import java.util.List;

import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;

public class ResultSetSorter
{
    private List<NodeRef> list;

    private String property;

    private Boolean ascending;

    private Comparator comparator;

    private NodeService nodeService;

    private NamespaceService namespaceService;
    
    public ResultSetSorter(List<NodeRef> list, String property,
            Boolean ascending, NodeService nodeService, NamespaceService namespaceService)
    {
        super();
        this.list = list;
        this.property = property;
        this.ascending = ascending;
        this.comparator = new ResultSetComparator();
        this.nodeService = nodeService;
        this.namespaceService = namespaceService;
    }

    /**
     * Runs the Quick Sort routine on the current dataset
     */
    public void sort()
    {
        if (this.list.size() != 0) {
            qsort(this.list, 0, this.list.size() - 1);
        }
    }

    /**
     * recursive Quicksort function.
     * 
     * @param v
     *            the array out of which to take a slice.
     * @param lower
     *            the lower bound of this slice.
     * @param upper
     *            the upper bound of this slice.
     */
    private void qsort(List v, final int lower, final int upper)
    {
        int sliceLength = upper - lower + 1;
        if (sliceLength > 1) {
            if (sliceLength < 7) {
                // Insertion sort on smallest datasets
                for (int i = lower; i <= upper; i++) {
                    if (this.ascending == true) {
                        for (int j = i; j > lower
                                && comparator.compare(v.get(j - 1), v.get(j)) > 0; j--) {
                            swap(v, j - 1, j);
                        }
                    }
                    else {
                        for (int j = i; j > lower
                                && comparator.compare(v.get(j - 1), v.get(j)) < 0; j--) {
                            swap(v, j - 1, j);
                        }
                    }
                }
            }
            else {
                int pivotIndex = partition(v, lower, upper);
                qsort(v, lower, pivotIndex);
                qsort(v, pivotIndex + 1, upper);
            }
        }
    }

    /**
     * Partition an array in two using the pivot value that is at the centre of
     * the array being partitioned.
     * 
     * This partition implementation based on that in Winder, R (1993)
     * "Developing C++ Software", Wiley, p.395. NB. This implementation (unlike
     * most others) does not guarantee that the split point contains the pivot
     * value. Unlike other implementations, it requires only < (or >) relation
     * and not both < and <= (or > and >=). Also, it seems easier to program and
     * to understand.
     * 
     * @param v
     *            the List out of which to take a slice.
     * @param lower
     *            the lower bound of this slice.
     * @param upper
     *            the upper bound of this slice.
     */
    private int partition(final List v, int lower, int upper)
    {
        Object pivotValue = v.get((upper + lower + 1) >> 1);

        int size = v.size();

        while (lower <= upper) {
            if (this.ascending == true) {
                while (comparator.compare(v.get(lower), pivotValue) < 0) {
                    lower++;
                }
                while (comparator.compare(pivotValue, v.get(upper)) < 0) {
                    upper--;
                }
            }
            else {
                while (comparator.compare(v.get(lower), pivotValue) > 0) {
                    lower++;
                }
                while (comparator.compare(pivotValue, v.get(upper)) > 0) {
                    upper--;
                }
            }
            if (lower <= upper) {
                if (lower < upper) {
                    swap(v, lower, upper);
                }
                lower++;
                upper--;
            }
        }

        return upper;
    }

    /**
     * Given the array and two indices, swap the two items in the array.
     */
    @SuppressWarnings("unchecked")
    protected void swap(final List v, final int a, final int b)
    {
        Object temp = v.get(a);
        v.set(a, v.get(b));
        v.set(b, temp);
    }

    private class ResultSetComparator implements Comparator
    {
        /**
         * @see org.alfresco.web.data.IDataComparator#compare(java.lang.Object,
         *      java.lang.Object)
         */
        public int compare(final Object obj1, final Object obj2)
        {
            if (obj1 == null && obj2 == null)
                return 0;
            if (obj1 == null)
                return -1;
            if (obj2 == null)
                return 1;

            // Here's where we need to worry about the property in the
            // ResultSetRow objects
            NodeRef row1 = (NodeRef) obj1;
            NodeRef row2 = (NodeRef) obj2;

            QName propName = QName.resolveToQName(ResultSetSorter.this.namespaceService, ResultSetSorter.this.property);
            // get the property values
            Object property1 = ResultSetSorter.this.nodeService.getProperty(row1, propName);
            Object property2 = ResultSetSorter.this.nodeService.getProperty(row2, propName);

            if (property1 == null && property2 == null)
                return 0;
            if (property1 == null)
                return -1;
            if (property2 == null)
                return 1;

            if (property1 instanceof String) {
                // Compare String should ignore case
                return ((String) property1).compareToIgnoreCase((String) property2);
            } else if (property1 instanceof Comparable) {
                return ((Comparable) property1).compareTo((Comparable) property2);
            } else {
                return property1.toString().compareTo(property2.toString());
            }
        }
    }
}
